package util;

import static util.FindFoodUtil.empty;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import models.Restaurant;

public class MapAccessibleHelper {

	private MapAccessibleHelper() {
	};

	public static Map<String, MappedField> discoverFields(Class<?> clazz,
			String... fieldNames) throws SecurityException, NoSuchMethodException {
		Map<String, MappedField> fields = new HashMap<String, MappedField>();
		for (String s : fieldNames) {
			Method getter = clazz.getMethod("get" + FindFoodUtil.upperFirst(s));
			Class type = getter.getReturnType();
			Method setter = clazz.getMethod("set" + FindFoodUtil.upperFirst(s),
					type);
			fields.put(s, new MappedField(s, type, getter, setter));
		}
		return Collections.unmodifiableMap(fields);
	}

	/**
	 * Sets field values from the given map.
	 */
	public static void setFields(MapAccessible object, Map<String, Object> values) {
		for (Map.Entry<String, Object> d : values.entrySet()) {
			try {
				MappedField m = object.getMappedFields().get(d.getKey());
				Class c = m.getFieldType();
				m.getSetter().invoke(object, c.cast(d.getValue()));
			} catch (Exception e) {
				// Skip this field.
			}
		}
	}

	/**
	 * Returns a map of fields which are different between this object and
	 * the values passed in.
	 */
	public static Map<String, Object> getDifferences(MapAccessible object,
			Map<String, Object> values) {
		Map<String, Object> differences = new HashMap<String, Object>();

		for (MappedField f : object.getMappedFields().values()) {
			try {
				String fieldName = f.getFieldName();
				if (!values.containsKey(fieldName))
					continue;

				String myValue = (String) f.getGetter().invoke(object);
				String compareValue = (String) values.get(fieldName);

				if (!empty(myValue) && !myValue.equals(compareValue)) {
					// My value is not null and the comparison value is different.
					differences.put(fieldName, compareValue);
				} else if (empty(myValue) && !empty(compareValue)) {
					// My value is null and the comparison value isn't.
					differences.put(fieldName, compareValue);
				}
			} catch (Exception e) {
				// Skip this field.
			}
		}

		return differences;
	}

	/**
	 * Gets this object's fields as a map.
	 */
	public static Map<String, Object> getFields(MapAccessible object) {
		Map<String, Object> fields = new HashMap<String, Object>();
		for (MappedField f : object.getMappedFields().values()) {
			try {
				fields.put(f.getFieldName(), f.getGetter().invoke(object));
			} catch (Exception e) {
				// Skip this field.
			}
		}
		return fields;
	}

	public static interface MapAccessible {

		public Map<String, MappedField> getMappedFields();

	}

	public static class MappedField {

		private String fieldName;
		private Class fieldType;
		private Method setter;
		private Method getter;

		public MappedField(String fieldName, Class fieldType, Method getter,
				Method setter) {
			this.fieldName = fieldName;
			this.fieldType = fieldType;
			this.setter = setter;
			this.getter = getter;
		}

		public String getFieldName() {
			return fieldName;
		}

		public Class getFieldType() {
			return fieldType;
		}

		public Method getGetter() {
			return getter;
		}

		public Method getSetter() {
			return setter;
		}
	}
}
